package com.ideaaedi.mybatis.data.security.interceptor;

import com.ideaaedi.mybatis.data.security.support.EncryptInfoHolder;
import com.ideaaedi.mybatis.data.security.support.EncryptParser;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Properties;

/**
 * 自定义Mybatis插件 - 实现数据库(入库/出库)敏感字段脱敏
 *
 * @author JustryDeng
 * @since 2021/2/10 22:40:59
 */
@Intercepts(value = {
        // 入
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
        // 出
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})
})
public class MybatisEncryptPlugin implements Interceptor {
    
    private static final Logger log = LoggerFactory.getLogger(MybatisEncryptPlugin.class);
    
    private final EncryptParser encryptParser;
    
    public MybatisEncryptPlugin(EncryptParser encryptParser) {
        this.encryptParser = encryptParser;
    }
    
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement mappedStatement = (MappedStatement)invocation.getArgs()[0];
        String mappedStatementId = mappedStatement.getId();
        EncryptInfoHolder encryptInfoHolder = encryptParser.determineEncryptInfoHolder(mappedStatementId);
        if (encryptInfoHolder == null) {
            log.debug("mappedStatementId [{}] match non-encryptInfoHolder. Operate database directly.", mappedStatementId);
            return invocation.proceed();
        }
        if (encryptInfoHolder.isNeedEncrypt()) {
            // 加密
            log.debug("mappedStatementId [{}] need encrypt.", mappedStatementId);
            Object rowParameter = invocation.getArgs()[1];
            invocation.getArgs()[1] = encryptParser.doEncrypt(rowParameter, encryptInfoHolder);
        }
        Object rowResult = invocation.proceed();
        if (encryptInfoHolder.isNeedDecrypt()) {
            // 解密
            log.debug("mappedStatementId [{}] need decrypt.", mappedStatementId);
            rowResult = encryptParser.doDecrypt(rowResult, encryptInfoHolder);
        }
        return rowResult;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // ignore
    }
}
